import requests, jsonpath, openpyxl, copy
from openpyxl.styles import Font
from Env import env_config as cfg
from Common.model import db_model
from Util.tool import dict_list_duplicate_removal_2, capture_str_variable
from Common.com_func import log

"""
    【 Swagger 解 析 】

    {
        "swagger":"2.0",
        "info":{},
        "host":"49.232.147.132:8080",
        "basePath":"/",
        "tags":[],
        "paths":{},
        "securityDefinitions":{},
        "definitions":{}
    }

    swagger：swagger版本号
    info：接口服务的项目信息：项目描述、名称、项目版本号等
    host：接口服务的主机名
    basePath：接口服务的起始根路径
    tags：接口服务中所有控制器的名称和描述
    paths：每个接口的详细信息：地址、参数、响应等
    securityDefinitions：接口的认证和授权方式信息
    definitions：每个接口方法中每一个参数的类型说明（ body中的参数信息 ）

"""


class swagger_prase(object):

    def __init__(self, pro_name, swagger_url):
        self.pro_name = pro_name
        self.swagger_url = swagger_url
        self.mongo_case_list = []    # 现有的用例列表
        self.all_cases_list = []     # swagger解析的用例列表(所有)
        self.new_cases_list = []     # swagger解析的用例列表(新增)
        self.update_cases_list = []  # swagger解析的用例列表(变动)
        self.name_exists_list = []   # swagger解析的用例列表(用例名称已存在的)

    def show(self):
        print("\nmongo现有用例列表(去重)：" + str(len(self.mongo_case_list)) + "\n")
        for case in self.mongo_case_list: print(case)
        print("\nswagger解析的用例列表(所有)：" + str(len(self.all_cases_list)) + "\n")
        for case in self.all_cases_list: print(case)
        print("\nswagger解析的用例列表(新增)：" + str(len(self.new_cases_list)) + "\n")
        for case in self.new_cases_list: print(case)
        print("\nswagger解析的用例列表(用例名称已存在的)：" + str(len(self.name_exists_list)) + "\n")
        for case in self.name_exists_list: print(case)
        print("\nswagger解析的用例列表(变动)：" + str(len(self.update_cases_list)) + "\n")
        for case in self.update_cases_list: print(case)

    def run(self):
        try:
            self.get_swagger_case_list()
            self.get_mongo_case_list()
            self.get_diff_case_list()
            self.import_excel()
            self.show()
            return "解析成功"
        except Exception as e:
            log.error("\n======= Swagger解析异常 =======\n")
            log.error(e)
            log.error("\n------------------------------\n")
            return "Swagger解析异常"

    def get_swagger_case_list(self):

        res = requests.get(url=self.swagger_url)
        data_source = res.json()

        # 获取接口信息数据（ 从根节点开始获取所有'key=paths'的'value'列表中的第一个）
        paths = jsonpath.jsonpath(data_source, '$.paths')[0]

        # 获取body正文体内容字典
        definitions_dict = jsonpath.jsonpath(data_source, '$.definitions')[0]

        for k, v in paths.items():
            interface_url = k    # 接口地址
            request_method = ""  # 方法类型
            interface_name = ""  # 接口描述
            content_type = ""    # 请求正文类型
            request_header = {}  # 请求头中的参数
            request_params = ""  # URL地址栏中的参数 -> ?pageNum=&pageSize=
            params_dict = {}     # 临时记录
            request_body = {}    # 请求正文中的参数 -> {'event': '', 'result': ''}
            body_schema = ""     # body正文体引用'definitions'中的内容

            for m, infos in v.items():
                request_method = m
                interface_name = infos.get('summary')
                if request_method == 'post':
                    content_type = infos.get('consumes')[0]
                parameters = infos.get('parameters')
                if parameters:
                    for p in parameters:
                        if p['in'] == 'header':
                            request_header[p['name']] = ''
                        elif p['in'] == 'query':
                            params_dict[p['name']] = ''
                        elif p['in'] == 'body':
                            body_schema = p['schema']['$ref']

            # 若存在 params_dict 则整合相应的参数信息
            if params_dict:
                request_params = "?"
                for param in params_dict.keys():
                    request_params += param + "=&"
                request_params = request_params[:-1]

            if request_method == "post":

                # 将 content_type 整合入 request_header ( 暂未考虑 上传文件 接口的情况 )
                if "Content-Type" not in request_header.keys():
                    request_header["Content-Type"] = content_type

                # 若存在 request_body 则获取并整合相应的参数信息
                if body_schema:
                    body_key = body_schema.split("/")[-1]
                    body_dict = definitions_dict.get(body_key).get('properties')
                    for key in body_dict.keys():
                        request_body[key] = ""

            # 将字典转换成字符串
            request_header = request_header != {} and str(request_header) or ""
            request_body = request_body != {} and str(request_body) or ""

            # 保持和Excel表格列的顺序一致
            self.all_cases_list.append(["", interface_name, interface_url, request_method.upper(), request_header,
                                        request_params, request_body, 1, "httpCode,httpRT", "200,500", "", "", "",
                                        False, "", "", False])

    def get_mongo_case_list(self):
        dm = db_model(pro_name=self.pro_name, table=cfg.TABLE_CASE, error_msg="swagger获取所有用例")
        self.mongo_case_list = dm.query_db()

    def get_diff_case_list(self):
        """
          获取有差异的用例列表（ 新增 + 改动 ）

         【 比 较 逻 辑 】
          < 判断 用例名称 >
          1.未重复：作为 新增 处理
          2.重复：< 判断 其他参数是否都一致 >
           （1）都一致：作为 已有 处理
           （2）有一处不一致：作为 改动 处理

         【 步 骤 】
          1.获取已有'用例名称'去重后的列表（重复的取第一条，去除不需要的字段）
          2.判断'用例名称'：
          （1）未重复的，保存入 新增列表
          （2）重复的，保存入 用例名称已存在列表
          3.判断'用例名称已存在列表'中 其他字段 是否都一致
          （1）若存在不一致的，保存入 更新列表

         【 不同字段的比较方式 】
          1.用例名称：已有用例 获取 :: 前半部分 进行比较
          2.请求地址：去除 URI 参数后进行比较（ mongo：<> | swagger：{} ）
          3.请求头文件|请求正文体： 比较keys()列表
          4.请求参数：去除 = 值后 进行比较

        """
        # 1.获取已有'用例名称'去重后的列表（重复的取第一条，去除不需要的字段）
        for case_dict in self.mongo_case_list:
            del case_dict["_id"]
            del case_dict["tag_name"]
            del case_dict["verify_mode"]
            del case_dict["compare_core_field_name_list"]
            del case_dict["expect_core_field_value_list"]
            del case_dict["expect_field_name_list"]
            del case_dict["upstream_relation_case"]
            del case_dict["downstream_relation_field_name_list"]
            del case_dict["is_depend"]
            del case_dict["depend_field_name_list"]
            del case_dict["depend_level"]
            del case_dict["case_status"]
            del case_dict["response_info"]
            del case_dict["depend_field_value_list"]
            del case_dict["downstream_relation_field_value_list"]
            del case_dict["actual_core_field_value_list"]
            del case_dict["actual_field_name_list"]
            del case_dict["result_core_field_value"]
            del case_dict["result_field_name_list"]
            del case_dict["test_result"]
            del case_dict["run_status"]
            del case_dict["update_time"]
            del case_dict["create_time"]
            for k, v in case_dict.items():
                if "interface_name" in k:
                    case_dict[k] = v.split("::")[0]
        self.mongo_case_list = dict_list_duplicate_removal_2(dict_list=self.mongo_case_list, key="interface_name")

        # 2.判断'用例名称'
        for case_list in self.all_cases_list:
            if case_list[1] in map(lambda x: x["interface_name"], self.mongo_case_list):
                self.name_exists_list.append(case_list)
            else:
                self.new_cases_list.append(case_list)

        # 3.判断'用例名称已存在列表'中 其他字段 是否都一致；若存在不一致的，保存入 更新列表
        for case_list in self.name_exists_list:
            # 获取 对应的 mongo 用例数据
            mongo_case_dict = list(filter(lambda x: x["interface_name"] == case_list[1], self.mongo_case_list))[0]
            # print(mongo_case_dict)

            # 比较 请求地址：去除 URI 参数后进行比较（ mongo：<> | swagger：{} ）
            interface_url_swagger = copy.copy(case_list[2])
            interface_url_mongo = copy.copy(mongo_case_dict["interface_url"])
            params_list_swagger = capture_str_variable(str=case_list[2], v_tag="{")
            params_list_mongo = capture_str_variable(str=mongo_case_dict["interface_url"], v_tag="<")
            if params_list_swagger:
                for param in params_list_swagger:
                    interface_url_swagger = interface_url_swagger.replace("{" + param + "}", "")
            if params_list_mongo:
                for param in params_list_mongo:
                    interface_url_mongo = interface_url_mongo.replace("<" + param + ">", "")
            # print(interface_url_swagger)
            # print(interface_url_mongo)
            if interface_url_swagger != interface_url_mongo:
                self.update_cases_list.append(case_list)
                continue

            # 比较 请求方式
            if case_list[3] != mongo_case_dict.get("request_method"):
                self.update_cases_list.append(case_list)
                continue

            # 比较 请求头文件：比较keys()列表
            header_list_swagger = case_list[4] and eval(case_list[4]).keys() or []
            header_list_mongo = mongo_case_dict.get("request_header") and eval(mongo_case_dict.get("request_header")).keys() or []
            if header_list_swagger != header_list_mongo:
                self.update_cases_list.append(case_list)
                continue

            # 比较 请求参数：去除 = 值后 进行比较
            params_swagger = case_list[5]
            params_mnongo = mongo_case_dict.get("request_params")
            if params_mnongo:
                p_list = capture_str_variable(str=params_mnongo + "&", v_tag="=")
                for p in p_list:
                    params_mnongo = params_mnongo.replace(p, "")
            if params_swagger != params_mnongo:
                self.update_cases_list.append(case_list)
                continue

            # 比较 请求正文体：比较keys()列表
            body_list_swagger = case_list[6] and eval(case_list[6]).keys() or []
            body_list_mongo = mongo_case_dict.get("request_body") and eval(mongo_case_dict.get("request_body")).keys() or []
            if body_list_swagger != body_list_mongo:
                self.update_cases_list.append(case_list)
                continue

    def import_excel(self):
        """
            将抓取的接口信息导入用例模板中

            1.保留Excel模板的头两行（ 行与列的起始位置为1 ）
            2.删除原有的数据（从第3行开始）：获取总行数，针对第3行进行循环删除
            3.添加新数据（从第3行开始）
        """
        wb = openpyxl.load_workbook(cfg.SWAGGER_CASE_FILE)
        sheet_all = wb.get_sheet_by_name("全部")
        sheet_new = wb.get_sheet_by_name("新增")
        sheet_update = wb.get_sheet_by_name("变动")

        # 删除原有的数据（从第3行开始）：获取总行数，针对第3行进行循环删除
        for i in range(sheet_all.max_row): sheet_all.delete_rows(3)
        for i in range(sheet_new.max_row): sheet_new.delete_rows(3)
        for i in range(sheet_update.max_row): sheet_update.delete_rows(3)

        # 添加新数据（从第3行开始）
        for row_i, case_info in enumerate(self.all_cases_list):
            for col_i, content in enumerate(case_info):
                sheet_all.cell(row=row_i + 3, column=col_i + 1, value=content).font = Font(name='Arial', size=14)

        for row_i, case_info in enumerate(self.new_cases_list):
            for col_i, content in enumerate(case_info):
                sheet_new.cell(row=row_i + 3, column=col_i + 1, value=content).font = Font(name='Arial', size=14)

        for row_i, case_info in enumerate(self.update_cases_list):
            for col_i, content in enumerate(case_info):
                sheet_update.cell(row=row_i + 3, column=col_i + 1, value=content).font = Font(name='Arial', size=14)
        # 更新数据
        wb.save(cfg.SWAGGER_CASE_FILE)


if __name__ == '__main__':

    pro_name = "pro_demo_1"
    host = "http://127.0.0.1:4567/deploy"  # java-deploy 项目
    # host = "http://localhost:1122/api_local"
    res = swagger_prase(pro_name=pro_name, swagger_url=host + cfg.SWAGGER_BASE_URL).run()
    print(res)



